
#ifndef LOD_GEN_DISP_H
#define LOD_GEN_DISP_H


#include <iostream>
using std::cout;
using std::endl;
using std::flush;

#include <sstream>
#include <string>
#include <vector>
#include <fstream>
#include <map>

#include <assert.h>

#include <stdlib.h>
#include <GL/glew.h>
#include <GL/glut.h>

#include "../types.h"
#include "../base/material.h"
#include "disp.h"

#include "../jeff/mesh.h"
#include "../jeff/pmesh.h"

#include "../light/lighting.h"
#include "../jeff_camera.h"

#include "../rply/rply.h"

//todo not okay
//
////split
////collapse
////rebuild, build
///////load_ob
///////build_emph_vec
////display
//
///////start_typing
///////stop_typing
///////key_typed
///////escape
///////backspace
////enter
///////keyboard





class gen_disp : public disp
{
    public:
    gen_disp(void* font, const GLfloat* menu_color4, const GLfloat* act_menu_color4, 
              int Width, int Height)
     : disp(font, menu_color4, act_menu_color4, 
             gen_disp::text, gen_disp::yinc_text, gen_disp::xoffset_text, 
             gen_disp::show_text, gen_disp::menu_pos,
             gen_disp::text_count,
             gen_disp::help_str, gen_disp::help_count),
       width(Width),
       height(Height),
       ob_loaded(false),
       typing(false),
       jmesh(NULL),
       //jpmeshes(8,NULL),
       jpmeshes(8),
       extra_base_meshes(8),
       active_lod(NONE_ACTIVE),
       show_lods(9,false),
       method(enum_method::QUADRICTRI),
       tr_perc(5),
       display_ob(false)
    {
        unsigned end=jpmeshes.size();
        for(unsigned i=0; i<end; i++)
            jpmeshes[i]=NULL;

        end=extra_base_meshes.size();
        for(unsigned i=0; i<end; i++)
            extra_base_meshes[i]=NULL;

        set_act_menu_pos(START_ACT_MENU_POS);
        //set_show_help(HELP_SHOWED_ON_START);
        show_help=false;
        tr_perc_adjust_menu();
        
    }

    // todo okay
    virtual ~gen_disp()
    {
        if(jmesh!=NULL)
            delete jmesh;

        unsigned end=jpmeshes.size();
        for(unsigned i=0; i<end; i++)
            if(jpmeshes[i]!=NULL)
                delete jpmeshes[i];

        end=extra_base_meshes.size();
        for(unsigned i=0; i<end; i++)
            if(extra_base_meshes[i]!=NULL)
                delete extra_base_meshes[i];
    }

    // todo dodac nazwy method pod wyswietlonymi obiektami, a takze ilosci aktywnych
    // oraz oryginalnych trojkatow 
    virtual void display(int width, int height)
    {

        if(ob_loaded)
        {
            if(show_help==false)
            {
                if(display_ob) 
                {
                    int moveright = (int)(width/6);
                    if(polygon_mode)
                        glPolygonMode( GL_FRONT, GL_FILL );
                    else
                        glPolygonMode( GL_FRONT, GL_LINE );

                    if(illuminated)
                        li.enable();
                    else {
                        li.disable();
                        grey();
                    }

                    glMatrixMode(GL_MODELVIEW);
                    glLoadIdentity();
                    gluLookAt(0,0,jcam.get_dist(), 0,0,0, 0,1,0);
                    glRotatef(jcam.get_elevation(),1,0,0);
                    glRotatef(jcam.get_azimuth(),0,1,0);


                    if(num_showed_lods()==1)
                    {
                        glViewport(0,0,width,height);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                                     //45
                        gluPerspective(60, (GLdouble)width / (GLdouble)height, 0.1, 30.0);
                        
                        display_num_jmesh(get_chosen_lod_num(0));
                        // draw the number of triangles 
                        li.disable();
                        quick_draw_string(width,height,
                                int2str(get_chosen_lod_num(0)).c_str(),20,60);
                        int n=get_chosen_lod_num(0);
                        quick_draw_string(width,height,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,40);

                        if(is_lod_active()) {
                            assert(get_chosen_lod_num(0)==active_lod);
                            glPolygonMode( GL_FRONT, GL_LINE );
                            mark_window(5,5,30,30,5,5);
                            set_poly_mode();
                        }
                        
                    }
                    else if(num_showed_lods()==2)
                    {
                        int w = (width-moveright);
                        int h = height/2;
                        std::vector<int> idxs=get_ordered_idxs(2);

                        glViewport(moveright,h,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);

                        display_num_jmesh(get_chosen_lod_num(idxs[0]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[0])).c_str(),20,40);
                        int n=get_chosen_lod_num(idxs[0]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                        if(is_lod_active()) {
                            glPolygonMode( GL_FRONT, GL_LINE );
                            mark_window(5,10,30,60,5,8);
                            set_poly_mode();
                        }
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        //glViewport(moveright,0,w,h);
                        w = width;
                        glViewport(0,0,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);
                        display_num_jmesh(get_chosen_lod_num(idxs[1]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[1])).c_str(),20,60);
                        n=get_chosen_lod_num(idxs[1]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,40);
                        
                    }
                    else if(num_showed_lods()==3)
                    {
                        int w = (width-moveright);
                        int h = height/2;
                        std::vector<int> idxs=get_ordered_idxs(3);

                        glViewport(moveright,h,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);

                        display_num_jmesh(get_chosen_lod_num(idxs[0]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[0])).c_str(),20,40);
                        int n=get_chosen_lod_num(idxs[0]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                        if(is_lod_active()) {
                            glPolygonMode( GL_FRONT, GL_LINE );
                            mark_window(5,10,30,60,5,8);
                            set_poly_mode();
                        }
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        //w /= 2;
                        //glViewport(moveright,0,w,h);
                        w = width/2;
                        glViewport(0,0,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);

                        display_num_jmesh(get_chosen_lod_num(idxs[1]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[1])).c_str(),20,60);
                        n=get_chosen_lod_num(idxs[1]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,40);
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        //glViewport(moveright+w,0,w,h);
                        glViewport(w,0,w,h);
                        display_num_jmesh(get_chosen_lod_num(idxs[2]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[2])).c_str(),20,40);
                        n=get_chosen_lod_num(idxs[2]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                    }
                    else if(num_showed_lods()==4)
                    {
                        int w = (width-moveright)/2;
                        int h = height/2;
                        std::vector<int> idxs=get_ordered_idxs(4);

                        glViewport(moveright,h,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);

                        display_num_jmesh(get_chosen_lod_num(idxs[0]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[0])).c_str(),20,40);
                        int n=get_chosen_lod_num(idxs[0]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                        if(is_lod_active()) {
                            glPolygonMode( GL_FRONT, GL_LINE );
                            mark_window(10,10,60,60,10,8);
                            set_poly_mode();
                        }
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        glViewport(moveright+w,h,w,h);
                        display_num_jmesh(get_chosen_lod_num(idxs[1]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[1])).c_str(),20,40);
                        n=get_chosen_lod_num(idxs[1]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        //glViewport(moveright,0,w,h);
                        w = width/2;
                        glViewport(0,0,w,h);
                        glMatrixMode(GL_PROJECTION);
                        glLoadIdentity();
                        gluPerspective(60, (GLdouble)w / (GLdouble)h, 0.1, 30.0);
                        display_num_jmesh(get_chosen_lod_num(idxs[2]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[2])).c_str(),20,60);
                        n=get_chosen_lod_num(idxs[2]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,40);
                        if(illuminated)
                            li.enable();
                        else
                            grey();

                        //glViewport(moveright+w,0,w,h);
                        glViewport(w,0,w,h);
                        display_num_jmesh(get_chosen_lod_num(idxs[3]));
                        li.disable();
                        quick_draw_string(w,h,
                                int2str(get_chosen_lod_num(idxs[3])).c_str(),20,40);
                        n=get_chosen_lod_num(idxs[3]);
                        quick_draw_string(w,h,
                            ("t: "+get_num_triangles_str(n)+" ~ "+
                             get_2dig_percent_str(get_num_triangles(n))+"%").c_str(),20,20);
                    }
                }// display_ob==true

                li.disable();
                glViewport(0,0,width,height);
                // draw the loaded object's name
                quick_draw_string(width,height,("Object: "+ob_name).c_str(),20,20);
            }//show_help==false
            else {
                li.disable();
                glViewport(0,0,width,height);
                disp_help(width,height);
            }

            comp_type emph_col[4] = {0.8, 0.5, 0.9, 1.0};
            disp_text(width, height, build_emph_vec(), emph_col);
        }
        else
        {
            li.disable();
            glViewport(0,0,width,height);
            disp_text(width, height);
        }
    }

    virtual disp* keyboard(unsigned char key, int x, int y)
    {
        disp* menu = this;
        if(typing) 
        {
            switch(key) 
            {
                // strange observation: glut gives just '\r'
                // as a newline character under Linux
                case '\r':
                    this->enter_key();
                    break;
                case '\b':
                    backspace_key();
                    break;
                case 27: // escape
                    menu = escape_key();
                    break;
                default:
                    key_typed(key);
            }
        }
        else // typing==false               
        {
            // a strange observation: glut gives just '\r'
            // as a newline character under Linux
            if(key=='\r')
                enter_key();
            else if(key=='\b')
                backspace_key();
            else if(key==27) // escape
                menu=escape_key();
            else if(key=='w')
                act_menu_pos_up();
            else if(key=='s')
                act_menu_pos_down();
            else if(key=='h')
                show_help = !show_help;

            else if(ob_loaded)
            {
                switch(key)
                {
                    case 'd': // display object on/off
                        display_ob=!display_ob;
                        break;
                    case 'S':
                        li.swap_shading(); // switch between flat/smooth shading
                        break;
                    case 'l': // turn on or off the lights
                        illuminated = !illuminated;
                        break;
                    case 'f':   // wireframe/filled
                        polygon_mode = !polygon_mode;
                        break;

                    case 'g': // collapse edges
                        collapse();
                        break;
                    case 'u': // split vertices
                        split();
                        break;
                    default:
                        ;
                }
            }


            /*switch(key) 
            {
                // strange observation: glut gives just '\r'
                // as a newline character under Linux
                case '\r':
                    this->enter_key();
                    break;
                case '\b':
                    this->backspace_key();
                    break;
                case 27: // escape
                    menu = this->escape_key();
                    break;

                case 'w':
                    this->act_menu_pos_up();
                    break;
                case 's':
                    this->act_menu_pos_down();
                    break;

                case 'd': // display object on/off
                    display_ob=!display_ob;
                    break;
                case 'S':
                    li.swap_shading(); // switch between flat/smooth shading
                    break;
                case 'l': // turn on or off the lights
                    illuminated = !illuminated;
                    break;
                case 'f':   // wireframe/filled
                    polygon_mode = !polygon_mode;
                    break;

                case 'g': // collapse edges
                    if(ob_loaded)
                        collapse();
                    break;
                case 'u': // split vertices
                    if(ob_loaded)
                        split();
                    break;

                default:
                    ;
            }*/
        }
        return menu;
    }

    virtual void keyboard_special(int key, int x, int y)
    {
        if(typing==false) {
            switch(key) {
                case GLUT_KEY_UP:
                    this->act_menu_pos_up();
                    break;
                case GLUT_KEY_DOWN:
                    this->act_menu_pos_down();
                    break;
            }
        }
        // if the user's typing we discard the special keys
    }

    
    virtual void reshape(int width, int height)
    {
        if(ob_loaded==false) {
            set_xstart_text(int(width * 1/2.5));
            set_ystart_text(int(height * 1/2.0));
        }
        else {
            set_xstart_text(15);
            set_ystart_text(height-30);
        }
        // store the updated values of width and height
        if(width!=this->width || height!=this->height) {
            this->width = width;
            this->height = height;
        }
    }

    
    virtual void init()
    {
        // viewport and projection m. are set in display()
        // (multiple viewports used)
        initOpenGL();
    }

    
    virtual void mouse(int button, int state, int x, int y)
    {
        if(button==GLUT_LEFT_BUTTON) {
            lmb_state = state;
            if(state==GLUT_DOWN) {
                rmb_state = GLUT_UP;    // restrict that only one button is pressed
                change_active(x,y);
            }
        }
        else if(button==GLUT_RIGHT_BUTTON) {
            rmb_state = state;
            if(state==GLUT_DOWN)
                lmb_state = GLUT_UP;
        }
    }

    
    virtual void motion(int x, int y)
    {
        jcam.mouseMotion(x,y,width,height, lmb_state==GLUT_DOWN, rmb_state==GLUT_DOWN);
    }

    // todo okay
    virtual void passive_motion(int x, int y)
    {
        jcam.mouseMotion(x,y,width,height, false, false);
    }




    private:
    
    void enter_key()
    {
        if(ob_loaded==false) 
        {
            typing = !typing;
            if(typing==true) 
            {
                start_typing();
            }
            else//(typing==false)
            {
                stop_typing();
                if(typed.length()>0) 
                    load_ob(typed);
                typed.clear();
            }
        }
        else { // ob_loaded==true
            int pos = get_act_menu_pos();
            switch(pos) 
            {
                // case 0 and case 1 represent the same menu position,
                // but just in different states
                case 0: // load an object (start typing)
                    typing=!typing;
                    assert(typing);
                    start_typing();
                    break;
                case 1: // load the object (of the name that the user has typed)
                    typing=!typing;
                    assert(!typing);
                    stop_typing();
                    if(typed.length()>0)
                        load_ob(typed);
                    typed.clear();
                    break;

                    // choose a simplification method to use
                case 3:
                case 4:
                case 5:
                case 6:
                    next_method();
                    break;

                //case 9:
                //    typing=!typing;
                //    assert(typing);
                //    start_typing();
                //    break;
                case 8: // the percentage
                    typing=!typing;
                    if(typing)
                        start_typing();
                    else {
                        stop_typing();
                        if(typed.length()>0) {
                            save_tr_percent(typed);
                            tr_perc_adjust_menu();
                            update_tr_num_from_perc();
                            tr_num_adjust_menu();
                        }
                        // it's certain that ob_loaded==true (we are inside "else" block)
                        typed.clear();
                    }
                    break;
                case 9: // the number of the triangles to collapse
                    typing=!typing;
                    if(typing)
                        start_typing();
                    else {
                        stop_typing();
                        if(typed.length()>0) {
                            save_tr_num(typed);
                            tr_num_adjust_menu();
                            update_tr_perc_from_num();
                            tr_perc_adjust_menu();
                        }
                        typed.clear();
                    }
                    break;

                case 11:
                case 12:
                case 13:
                case 14:
                case 15:
                case 16:
                case 17:
                case 18:
                case 19:
                    check_lod(pos-11);
                    break;

                case 20: // edge collapse (generate coaser LOD)
                    //if(ob_loaded)
                    collapse();
                    break;
                case 21: // vertex split (restore more details)
                    //if(ob_loaded)
                    split();
                    break;
                //case 22:
                 //   save_lods("/tmp/lodss");
                  //  break;
                
                // case 22 and case 23 represent the same menu position,
                // but in different states
                case 22: // save lods (start typing)
                    typing=!typing;
                    assert(typing==true);
                    start_typing();
                    break;
                case 23: // save lods (under the name you've just typed)
                    typing=!typing;
                    assert(typing==false);
                    stop_typing();
                    if(typed.length()>0)
                        save_lods(typed);
                        
                    typed.clear();
                    break;
            }
        }
    }

    
    //! return the number of displayed lods
    int num_showed_lods()
    {
        int n = 0;
        unsigned end=show_lods.size();
        for(unsigned i=0; i<end; i++)
            n+=(int)show_lods[i];
        return n;
    }

    
    std::vector<int> build_emph_vec()
    {
        std::vector<int> v;
        unsigned end=show_lods.size();
        for(unsigned i=0; i<end; i++)
            if(show_lods[i])
                v.push_back(11+i);
        
        return v;
    }

    
    void backspace_key()
    {
        if(typing && typed.length()>0) 
        {
            std::string base;
            int p = get_act_menu_pos();
            if(p==1)  // load an object
                base = std::string(text[1]);
            else if(p==8)
                base = std::string(text[8]);
            else if(p==9)
                base = std::string(text[9]);
            else if(p==23)
                base = std::string(text[23]);
            else {
                ;
                assert(false);  // that's why we have all this long if block
            }
            
            typed = typed.substr(0, typed.length()-1);
            std::string s = std::string(base) + typed;
            change_text(p, s.c_str());  // and this time shorter, we avoid using the long block
                                        // by using position p
        }
    }

    
    disp* escape_key()
    {
        disp* menu = this;
        if(typing) {
            typing = false;
            typed.clear();
            
            stop_typing();
        }
        else
            menu = change_menu(false); // go back
        return menu;
    }

    
    // todo okay
    void start_typing()
    {
        if(ob_loaded==false) {
            menu_pos_visible(0, false);
            menu_pos_visible(1, true);
            set_act_menu_pos(1);
        }
        else {
            int p = get_act_menu_pos();
            if(p==0) { // load an object
                menu_pos_visible(0, false);
                menu_pos_visible(1, true);
                set_act_menu_pos(1);
            }
            else if(p==8) { // percent of the triangles to remove
                change_text(8, text[8]);
            }
            else if(p==9) { // a number of triangles to remove
                change_text(9, text[9]);
            }
            else if(p==22) { // save
                menu_pos_visible(22, false);
                menu_pos_visible(23, true);
                set_act_menu_pos(23);
            }
        }
    }

    
    void stop_typing()
    {
        if(ob_loaded==false) {
            menu_pos_visible(0, true);
            menu_pos_visible(1, false);
            change_text(1, text[1]);
            set_act_menu_pos(0);
        }
        else {
            int p = get_act_menu_pos();
            if(p==1) { // load an object
                menu_pos_visible(0, true);
                menu_pos_visible(1, false);
                change_text(1, text[1]); // bring back the base label
                set_act_menu_pos(0);
            }
            else if(p==8) { // a percentage of the triangles to remove
                tr_perc_adjust_menu();  // see by analogy the notice below
            }
            else if (p==9) { // a number of triangles to remove
                tr_num_adjust_menu();
                // we changed a bit the concept of handling the menu in this case;
                // there is exactly one position, so visibility and focus remains
                // we only take care about the correct text displayed (which will
                // change soon if the number of triangles to remove has changed,
                // but this is handled in another place)
            }
            else if(p==23) { // save
                menu_pos_visible(22, true);
                menu_pos_visible(23, false);
                change_text(23, text[23]);
                set_act_menu_pos(22);
            }
        }
    }

    
    /*! we call this method only when the user is typing some value
    // (as opposed to e.g. switching by the user some display options
    // by typing one-key shortcuts) */
    void key_typed(unsigned char key)
    {
        if(ob_loaded==false) {
            typed += key;
            std::string s = std::string(text[1]) + typed;
            change_text(1, s.c_str());
        }
        else { // ob_loaded==true
            int p = get_act_menu_pos();
            if(p==1) { // load an object
                typed += key;
                std::string s = std::string(text[1]) + typed;
                change_text(1, s.c_str());
            }
            else if(p==8) { // a percentage of the triangles to remove
                if(key=='.' && typed.find('.')==std::string::npos ||
                   key>='0' && key<='9') {
                    typed += key;
                    std::string s = std::string(text[8]) + typed;
                    change_text(8, s.c_str());
                }
            }
            else if(p==9) { // a number of triangles to remove
                if(key>='0' && key<='9') {
                    typed += key;
                    std::string s = std::string(text[9] + typed);
                    change_text(9, s.c_str());
                }
            }
            else if(p==23) { // save lods
                typed += key;
                std::string s = std::string(text[23]) + typed;
                change_text(23, s.c_str());
            }
        }
    }

    // todo okay
    // adjust the menu when an object gets loaded/unloaded
    void ob_adjust_menu()
    {
        menu_pos_visible(2, ob_loaded);
        menu_pos_visible(3+method, ob_loaded);  // simpl. method type
        menu_pos_visible(7, 22, ob_loaded);

        //method
        menu_pos_active(3+method, ob_loaded);

        //params
        menu_pos_active(8, ob_loaded);
        menu_pos_active(9, ob_loaded);

        // LODs
        menu_pos_active(11, ob_loaded);
        menu_pos_active(12, ob_loaded);
        menu_pos_active(13, ob_loaded);
        menu_pos_active(14, ob_loaded);
        menu_pos_active(15, ob_loaded);
        menu_pos_active(16, ob_loaded);
        menu_pos_active(17, ob_loaded);
        menu_pos_active(18, ob_loaded);
        menu_pos_active(19, ob_loaded);

        // collapse and split
        menu_pos_active(20, ob_loaded);
        menu_pos_active(21, ob_loaded);

        // save
        menu_pos_active(22, ob_loaded);
    }

    // todo okay
    //! updates the displayed value of the percentage of the triangles to remove
    void tr_perc_adjust_menu()
    {
        std::ostringstream ss;
        //ss<<tr_perc;
        //std::string s = std::string(text[8]) + ss.str();
        ss<<text[8]<<tr_perc;
        //change_text(8, s.c_str());
        change_text(8, ss.str().c_str());
    }

    // todo okay
    //! updates the displayed number of the triangles to remove
    void tr_num_adjust_menu()
    {
        std::ostringstream s;
        s<<text[9]<<tr_num;
        change_text(9,s.str().c_str());
    }

    // todo okay
    void load_ob(std::string filename)
    {
        if(is_ply(filename)==false) {
            cout<<"error: file "<<filename<<" is of unknown format or it does not exist"
                <<endl<<flush;
            return;
        }

        if(jmesh!=NULL)
           delete jmesh;
        
        jmesh = new jeff::Mesh((char*)filename.c_str());
        
        if(jmesh->getNumVerts()>0 && jmesh->getNumTriangles()>0) 
        {
            jmesh->Normalize();

            
            // remove the meshes of the old object, if any
            unsigned end=jpmeshes.size();
            for(unsigned i=0; i<end; i++)
                if(jpmeshes[i]!=NULL) {
                    delete jpmeshes[i];
                    jpmeshes[i]=NULL;
                }

            end=show_lods.size();
            for(unsigned i=1; i<end; i++)
                show_lods[i]=false;
            show_lods[0]=true;  // display the original object

            active_lod=NONE_ACTIVE;

            update_tr_num_from_perc();
            tr_num_adjust_menu();

            ob_name=filename;
            ob_loaded=true;
            display_ob=true;
            illuminated=true;
            polygon_mode=true;  // solid
            show_help=false;
            li.set_flat_shading();
            jcam.resetOrientation();

            cout<<"Loaded "<<typed<<endl<<flush;
        }
        else 
        { // bad mesh or an empty mesh
            delete jmesh;
            jmesh=NULL;

            unsigned end=jpmeshes.size();
            for(unsigned i=0; i<end; i++)
                if(jpmeshes[i]!=NULL) {
                    delete jpmeshes[i];
                    jpmeshes[i]=NULL;
                }

            // preserve the choice of methods as it is

            ob_loaded=false;
            cout<<"Failed to load \""<<filename<<"\" (or it was an empty object)."<<endl<<flush;
        }

        reshape(width,height);
        ob_adjust_menu();
    }

    // todo okay
    void save_tr_percent(std::string percent)
    {
        std::istringstream ss(percent);
        float f;
        ss >> f;
        if(ss && f>0 && f<100)
            tr_perc = f;
    }

    // todo okay
    void save_tr_num(std::string num)
    {
        std::istringstream ss(num);
        int n;
        ss >> n;
        if(ss)
        {
            // take the active LOD's mesh as a base
            if(is_lod_active() && n>0 && n<get_num_triangles(active_lod))
                tr_num = n;
            // take the original object as a base
            else if(is_lod_active()==false && n>0 && n<get_num_triangles(0))
                tr_num = n;
        }
    }

    // todo okay
    void initOpenGL()
    {
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

        li.disable_all();
        //todo probably it's necessary to bring back the light's parameters
        // (they might have changed in another screen)
	// Enable lights and lighting
	glEnable(GL_LIGHT0); // default value is (1.0, 1.0, 1.0, 1.0)
	glEnable(GL_LIGHTING);

	glEnable(GL_CULL_FACE); // backface culling

        comp_type diff[3] = {0.8, 0.8, 0.8}; // grey
        comp_type spec[3] = {0, 0, 0};
        material<comp_type> m(diff,spec);
        m.shininess=128;
        m.set();

	//glShadeModel(GL_SMOOTH); // Gouraud shading
        li.set_flat_shading();
    }

    
    void display_jmesh(jeff::Mesh* mesh)
    {
        assert(mesh!=NULL);
        glBegin(GL_TRIANGLES);
        int end=mesh->getNumTriangles();
        for (int i=0; i<end; i++)
        {
            jeff::triangle t=mesh->getTri(i);
            float a[3];
            //if (jmesh->getTri(i, t) && t.isActive())
            {
                if (li.is_smooth_shading())
                {
                        const jeff::vertex& v1 = t.getVert1vertex();
                        const float* pFltV1Norm = v1.getArrayVertNorms();
                        glNormal3fv(pFltV1Norm);
                        const float* pFltArray1 = v1.getArrayVerts();
                        glVertex3fv(pFltArray1);
                        const jeff::vertex& v2 = t.getVert2vertex();
                        const float* pFltV2Norm = v2.getArrayVertNorms();
                        glNormal3fv(pFltV2Norm);
                        const float* pFltArray2 = v2.getArrayVerts();
                        glVertex3fv(pFltArray2);
                        const jeff::vertex& v3 = t.getVert3vertex();
                        const float* pFltV3Norm = v3.getArrayVertNorms();
                        glNormal3fv(pFltV3Norm);
                        const float* pFltArray3 = v3.getArrayVerts();
                        glVertex3fv(pFltArray3);

                        // flip it
                        a[0] = -pFltV3Norm[0];
                        a[1] = -pFltV3Norm[1];
                        a[2] = -pFltV3Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray3);
                        a[0] = -pFltV2Norm[0];
                        a[1] = -pFltV2Norm[1];
                        a[2] = -pFltV2Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray2);
                        a[0] = -pFltV1Norm[0];
                        a[1] = -pFltV1Norm[1];
                        a[2] = -pFltV1Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray1);
                }
                else // flat shaded
                {
                        const float* pFltArrayN = t.getNormal();
                        glNormal3fv(pFltArrayN);
                        const float* pFltArray1 = t.getVert1();
                        glVertex3fv(pFltArray1);
                        const float* pFltArray2 = t.getVert2();
                        glVertex3fv(pFltArray2);
                        const float* pFltArray3 = t.getVert3();
                        glVertex3fv(pFltArray3);

                        a[0] = -pFltArrayN[0];
                        a[1] = -pFltArrayN[1];
                        a[2] = -pFltArrayN[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray3);
                        glVertex3fv(pFltArray2);
                        glVertex3fv(pFltArray1);
                }
            }
        }
        glEnd();
    }

    // todo okay
    void display_jmesh(jeff::PMesh* mesh)
    {
        assert(mesh!=NULL);
        glBegin(GL_TRIANGLES);
        for (int i=0; i<mesh->numTris(); i++)
        {
            jeff::triangle t;
            float a[3];
            if (mesh->getTri(i, t) && t.isActive())
            {
                if (li.is_smooth_shading())
                {
                        const jeff::vertex& v1 = t.getVert1vertex();
                        const float* pFltV1Norm = v1.getArrayVertNorms();
                        glNormal3fv(pFltV1Norm);
                        const float* pFltArray1 = v1.getArrayVerts();
                        glVertex3fv(pFltArray1);
                        const jeff::vertex& v2 = t.getVert2vertex();
                        const float* pFltV2Norm = v2.getArrayVertNorms();
                        glNormal3fv(pFltV2Norm);
                        const float* pFltArray2 = v2.getArrayVerts();
                        glVertex3fv(pFltArray2);
                        const jeff::vertex& v3 = t.getVert3vertex();
                        const float* pFltV3Norm = v3.getArrayVertNorms();
                        glNormal3fv(pFltV3Norm);
                        const float* pFltArray3 = v3.getArrayVerts();
                        glVertex3fv(pFltArray3);

                        // flip it
                        a[0] = -pFltV3Norm[0];
                        a[1] = -pFltV3Norm[1];
                        a[2] = -pFltV3Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray3);
                        a[0] = -pFltV2Norm[0];
                        a[1] = -pFltV2Norm[1];
                        a[2] = -pFltV2Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray2);
                        a[0] = -pFltV1Norm[0];
                        a[1] = -pFltV1Norm[1];
                        a[2] = -pFltV1Norm[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray1);
                }
                else
                {
                        const float* pFltArrayN = t.getNormal();
                        glNormal3fv(pFltArrayN);
                        const float* pFltArray1 = t.getVert1();
                        glVertex3fv(pFltArray1);
                        const float* pFltArray2 = t.getVert2();
                        glVertex3fv(pFltArray2);
                        const float* pFltArray3 = t.getVert3();
                        glVertex3fv(pFltArray3);

                        a[0] = -pFltArrayN[0];
                        a[1] = -pFltArrayN[1];
                        a[2] = -pFltArrayN[2];
                        glNormal3fv(a);
                        glVertex3fv(pFltArray3);
                        glVertex3fv(pFltArray2);
                        glVertex3fv(pFltArray1);
                }
            }
        }
        glEnd();
    }


    // todo okay
                        // numbers start from 0 (since we are in C++)
    void display_num_jmesh(int num)
    {
        // display the original object 
        // (also in case it was queried to display a pmesh which does not exist)
        if(num==0 || jpmeshes[num-1]==NULL) { 
            assert(jmesh!=NULL);
            display_jmesh(jmesh);
        }
        else {
            assert(jpmeshes[num-1]!=NULL);
            display_jmesh(jpmeshes[num-1]);
        }
    }


    // todo okay
    int get_num_triangles(int num)
    {
        // return the number of triangles in the original object
        // (also in case a non-existing pmesh was chosen)
        
        //cout<<num<<endl<<flush;
        if(num==0 || jpmeshes[num-1]==NULL) { 
            assert(jmesh!=NULL);
            return jmesh->getNumTriangles();
        }
        else {
            assert(jpmeshes[num-1]!=NULL);
            return jpmeshes[num-1]->numVisTris();
        }
    }


    std::string int2str(int i)
    {
        std::ostringstream os;
        os<<i;
        return os.str();
    }

    // todo okay
    std::string get_num_triangles_str(int num)
    {
        //int n=get_num_triangles(num);
        //std::ostringstream os;
        //os<<n;
        //return os.str();
        return int2str(get_num_triangles(num));
    }

    // todo okay
    //! updates the number of triangles to collapse (using the earlier set percentage 
    /*! and the active LOD; if no LOD is active, the original object is used as a base)*/
    void update_tr_num_from_perc()
    {
        if(is_lod_active()==false) 
            // take the specified percent of the original mesh'es triangles
            tr_num=int(tr_perc/100*get_num_triangles(0));
        else
            // take the specified percent of the active LOD's mesh'es triangles
            tr_num=int(tr_perc/100*get_num_triangles(active_lod));
    }

    // todo okay
    //! updates the percentage that the earlier set number of triangles to collapse 
    // correspond to (as a base the active LOD's mesh is used, or the original mesh
    // if no LOD is active)
    void update_tr_perc_from_num()
    {
        float tp;
        if(is_lod_active()==false)  // see the notice above
            tp = ((float)tr_num)/get_num_triangles(0);
        else
            tp = ((float)tr_num)/get_num_triangles(active_lod);

        tp *= 100; //now it's in percents
        tr_perc = ((int)(tp*100))/100.0; // take just two digits after the dot
    }

    void collapse()
    {                                   // we do not collapse the original object
        if(active_lod!=NONE_ACTIVE && active_lod!=0)
        {
                        // the active lod number includes the original mesh, 
                        // but jpmeshes contains only LOD meshes
            if(jpmeshes[active_lod-1]==NULL)    // active 
                jpmeshes[active_lod-1]=new jeff::PMesh(jmesh,get_edge_cost());
            else {
                //cout<<jpmeshes[active_lod-1]->getEdgeCost()<<endl<<flush;
                //cout<<get_edge_cost()<<endl<<flush;
                if(get_edge_cost()!=jpmeshes[active_lod-1]->getEdgeCost())  
                {
                    // alright, let us put some overhead to this Swiss knife :)
                    // the following 4 lines add the possibility to change 
                    // dynamically the simplification method, i.e. a user can
                    // simplify an object with one method to some level, and
                    // continue the simplification process using another one
                    // (and another, and another...)
                    // however, the reverse process (vertex split) is only
                    // possible to the level of the last method change
                    // and yea, the overhead... it's because every pmesh has to have
                    // its base mesh (normally we would have just one base mesh for
                    // all of the pmeshes)
                    jeff::Mesh* newm = new jeff::Mesh(jpmeshes[active_lod-1]->getChangedMesh());
                    if(extra_base_meshes[active_lod-1]!=NULL)
                        delete extra_base_meshes[active_lod-1];
                    //extra_base_meshes.push_back(newm);
                    extra_base_meshes[active_lod-1]=newm;
                    delete jpmeshes[active_lod-1];
                    jpmeshes[active_lod-1]=new jeff::PMesh(newm, get_edge_cost());
                    cout<<"new progressive mesh generated"<<endl<<flush;
                }
            }

            jeff::PMesh* mesh=jpmeshes[active_lod-1];
            bool ret=true;
            int i;
            int end=tr_num/2; // one edge collapse normally collapses 2 triangles
            for(i=0; ret && i<end; i++) {
                ret = mesh->collapseEdge();
            }
            if(ret)
                cout<<end<<" edges collapsed"<<endl<<flush;
            else
                cout<<i-1<<" edges collapsed okay and then an error occured; "
                    <<"isn't the object collapsed totally?"<<endl<<flush;
        }
    }

    
    void split()
    {
            // if it's NULL then we have to create a new pmesh, but it's 
            // unnecessary since we are splitting vertices (a vertex split gives
            // new triangles, but the upper bound is all of the triangles 
            // of the original object)
        if(active_lod!=NONE_ACTIVE && active_lod!=0 && jpmeshes[active_lod-1]!=NULL)
        {
            if(get_edge_cost()==jpmeshes[active_lod-1]->getEdgeCost())
            {
                jeff::PMesh* mesh=jpmeshes[active_lod-1];
                bool ret=true;
                int i;
                int end=tr_num/2;
                for(i=0; ret && i<end; i++) {
                    ret = mesh->splitVertex();
                }
                if(ret)
                    cout<<end<<" vertices split"<<endl<<flush;
                else
                    cout<<i-1<<" vertices split fine and then an error occured; "
                        <<"isn't the object entirely reconstructed?"<<endl<<flush;
            }
            else
                cout<<"the simplification method has changed! we can't undo the changes"
                    <<endl<<flush;
        }
    }

    
    void grey()
    {
        // Make everything grey
        glColor3ub(128, 128, 128);
    }

    
    bool is_lod_active()
    {
        return active_lod!=NONE_ACTIVE;
    }

    void next_method()
    {
        menu_pos_visible(3+method, false);  // turn off the previous method
        menu_pos_active(3+method, false);
        
        if((method=(enum_method::type)(method+1))==enum_method::ENDENUM)
            method=(enum_method::type)0;

        menu_pos_visible(3+method, true);   // turn on the new method
        menu_pos_active(3+method, true);

        set_act_menu_pos(3+method);
    }

    bool is_ply(std::string filename)
    {
        std::ifstream is;
        std::string word;
        bool ret;
        is.open(filename.c_str());
        if(!is)
            return false;

        is>>word;
        if(is && word=="ply")
            ret=true;
        else
            ret=false;
        is.close();
        return ret;
    }

    void check_lod(int num)
    {
        if(show_lods[num]) {
            show_lods[num]=false;
            if(active_lod==num)
                active_lod=NONE_ACTIVE;
        }
        else if(num_showed_lods()<4) {  // maximum 4 LODs displayed
            show_lods[num]=true;
            // the active object stays as it is
        }
    }

    jeff::PMesh::EdgeCost get_edge_cost()
    {
        jeff::PMesh::EdgeCost ec;
        if(method==enum_method::QUADRIC)
            ec=jeff::PMesh::QUADRIC;
        else if(method==enum_method::QUADRICTRI)
            ec=jeff::PMesh::QUADRICTRI;
        else if(method==enum_method::MELAX)
            ec=jeff::PMesh::MELAX;
        else { //if(method==enum_method::SHORTEST) 
            assert(method==enum_method::SHORTEST); 
            ec=jeff::PMesh::SHORTEST;
        }

        return ec;
    }

    void mark_window(int x,int y,int lenw, int lenh,int width,int height)
    {
        glDisable(GL_DEPTH_TEST);
        push_ortho(this->width,this->height);

        GLCOLOR3(0.8,0.9,0.05);
        glBegin(GL_QUADS);
            GLVERTEX2(x,y);
            GLVERTEX2(x+lenw,y);
            GLVERTEX2(x+lenw,y+height);
            GLVERTEX2(x,y+height);

            GLVERTEX2(x,y);
            GLVERTEX2(x+width,y);
            GLVERTEX2(x+width,y+lenh);
            GLVERTEX2(x,y+lenh);
        glEnd();

        pop_ortho();
        glEnable(GL_DEPTH_TEST);
    }

    void change_active(int x,int y)
    {
        int moveright = (int)(width/6);  // same as in display()
        int n;
        y=height-y; // translates window coordinates to drawing coordinates
                    //  (0,0) in (left,top) to (left,bottom)
        switch(num_showed_lods())
        {                           // returns a number of the clicked window
                                    // 0==none (outside of any window)
                                    //
                                    // 1        1        1          1 2
                                    //          2       2 3         3 4
            case 0:
                n=0;
                break;
            case 1:
                n=1;
                break;
            case 2:
                if(x>moveright && y>height/2)
                    n=1;
                else if(y<=height/2)
                    n=2;
                else
                    n=0;
                break;
            case 3:
                if(x>moveright && y>height/2)
                    n=1;
                else if(x<width/2 && y<=height/2)
                    n=2;
                else if(x>=width/2 && y<=height/2)
                    n=3;
                else
                    n=0;
                break;
            case 4:
                if(x>moveright && x<moveright+(width-moveright)/2 && y>height/2)
                    n=1;
                else if(x>moveright && y>height/2)
                    n=2;
                else if(x<width/2 && y<=height/2)
                    n=3;
                else if(x>=width/2 && y<=height/2)
                    n=4;
                else
                    n=0;
                break;
            default:
                n=-1;
        }
        assert(n!=-1);

        if(n!=0)    // 0 means we've clicked outside of all windows
        {
            // the active LOD is always drawn in window 1, while the other LODs are drawn 
            // in the next windows with the order specified by their number
            // (only the chosen LODs are drawn, up to 4 of them)
            int prev_act_lod=active_lod;
            if(is_lod_active()==false) 
                active_lod=get_chosen_lod_num(n-1);
            else {
                //if(n==1) ; // we've clicked on already active object, so we do nothing
                //else
                if(n!=1) {
                    if(get_active_lod_idx()+1>=n)
                        active_lod=get_chosen_lod_num(n-2);
                    else
                        active_lod=get_chosen_lod_num(n-1);
                }
            }
            if(prev_act_lod!=active_lod) { //the active LOD has changed
                int n=get_num_triangles(active_lod);
                if(tr_num >= n) { // now it's too many
                    if(n>1) {
                        tr_num=(int)(tr_perc/100*n);
                        if(tr_num==0)
                            tr_num=1;
                    }
                    else
                        tr_num=1;//since there is not much sense in collapsing 0 triangles
                }
                
                tr_num_adjust_menu();
                update_tr_perc_from_num();
                tr_perc_adjust_menu();
            }
        }
        //cout<<active_lod<<endl<<flush;
    }

    /*! returns the number of a LOD of the index given 
    // (by an index it is meant a position in the list of chosen LODs) */
    int get_chosen_lod_num(int idx)
    {
        unsigned end=show_lods.size();
        for(unsigned i=0; i<end; i++) {
            if(show_lods[i]) {
                if(idx==0)
                    return i;
                else
                    idx--;
            }
        }
        assert(false);
        return -100; // random very bad value
    }

    /*! returns the index (position) of the active LOD 
    //  in the list of chosen LODs
    // the indices start from 0 */
    int get_active_lod_idx()
    {
        int n=0;
        unsigned end=show_lods.size();
        for(unsigned i=0; i<end; i++)
            if(show_lods[i]) {
                if((int)i==active_lod)
                    return n;
                else
                    n++;
            }
        assert(false);
        return -100;
    }

    void set_poly_mode()
    {
        if(polygon_mode)
            glPolygonMode( GL_FRONT, GL_FILL );
        else
            glPolygonMode( GL_FRONT, GL_LINE );
    }

    std::vector<int> get_ordered_idxs(int n)
    {
        std::vector<int> v;
        if(is_lod_active()) {
            int a=get_active_lod_idx();
            v.push_back(a);
            for(int i=0; i<n; i++)
                if(i!=a)
                    v.push_back(i);
        }
        else {
            for(int i=0; i<n; i++)
                v.push_back(i);
        }
        return v;
    }

    int get_2dig_percent(int nvert)
    {
        if(jmesh!=NULL) {
            float f=(float)nvert/jmesh->getNumTriangles();
            return (int)(f*10000)/100;
        }
        else
            return -1;
    }

    std::string get_2dig_percent_str(int nvert)
    {
        return int2str(get_2dig_percent(nvert));
    }


    //! saves given mesh to a ply file 
    /*! either a new file is created, or the mesh is appended to existing ply file 
     * (when append is set to true) */
    bool save_one_lod(jeff::Mesh* mesh, std::string lod_info, std::string filename, 
                    e_ply_storage_mode storage_mode, bool append=false)
    {
        p_ply oply;
        int ret;

        // create (or append to) the output ply file
        oply = ply_create_append(filename.c_str(), storage_mode, NULL, append);
        if(!oply) {
            cout<<"error: unable to open file '"<<filename<<"' for writing"<<endl<<flush;
            return false;
        }

        // add lod's number
        if(!ply_add_obj_info(oply,lod_info.c_str())) {
            cout<<"error: unable to add object info to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply); // we don't care about the result              [*]
                            // since there has been an error already
            return false;
        }

        // add vertex element and its properties
        if(!ply_add_element(oply,"vertex",mesh->getNumVerts())) {
            cout<<"error: unable to add element "<<"'vertex'"<<" to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }

                    // we store all of the meshes using Mesh and PMesh,
                    // and they keep the data in float format
        ret = ply_add_scalar_property(oply,"x",PLY_FLOAT);
        ret = ret && ply_add_scalar_property(oply,"y",PLY_FLOAT);
        ret = ret && ply_add_scalar_property(oply,"z",PLY_FLOAT);
        if(!ret) {
            cout<<"error: unable to add some of "<<"'vertex'"<<" element"
                <<" properties to ply object of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }


        // add face (triangle) element and its properties
        if(!ply_add_element(oply,"face",mesh->getNumTriangles())) {
            cout<<"error: unable to add element "<<"'face'"<<" to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }

        if(!ply_add_list_property(oply,"vertex_indices",PLY_UINT8,PLY_INT)) {
            cout<<"error: unable to add 'vertex_indices' property "<<"of 'face' element"
                <<" to ply object of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }
        

        // write the header
        if(!ply_write_header(oply)) {
            cout<<"error: failed writing header of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);    // [*]
            return false;
        }

        // write the vertices
        int end=mesh->getNumVerts();
        jeff::Vec3 v;
        for(int i=0; i<end; i++) {
            v=(mesh->getVertex(i)).getXYZ();
            ret=ply_write(oply,v.x);
            ret=ret && ply_write(oply,v.y);
            ret=ret && ply_write(oply,v.z);
            if(!ret) {
                cout<<"error: failed writing vertex "<<i<<"th"<<endl<<flush;
                ply_close(oply);// [*]
                return false;
            }
        }

        // write the triangles
        end=mesh->getNumTriangles();
        int v1,v2,v3;
        for(int i=0; i<end; i++) {
            (mesh->getTri(i)).getVerts(v1,v2,v3);
            ret=ply_write(oply,3);
            ret=ret && ply_write(oply,v1);
            ret=ret && ply_write(oply,v2);
            ret=ret && ply_write(oply,v3);
            if(!ret) {
                cout<<"error: failed writing triangle "<<i<<"th"<<endl<<flush;
                ply_close(oply);// [*]
                return false;
            }
        }

        if (!ply_close(oply)) {
            cout<<"error closing file '"<<filename<<"'"<<endl<<flush;
            return false;
        }
        else {
            cout<<"saved "<<lod_info<<" to file '"<<filename<<"'"<<endl<<flush;
            return true;
        }
    }


    //! saves given pmesh to a ply file 
    /*! either a new file is created, or the mesh is appended to existing ply file 
     * (when append is set to true) 
     * only active data (active vertices and active triangles) is saved 
     * (so all of primitives removed during simplification are skipped) */
    bool save_one_lod(jeff::PMesh* pmesh, std::string lod_info, std::string filename, 
                    e_ply_storage_mode storage_mode, bool append=false)
    {
        jeff::Mesh* mesh;
        p_ply oply;
        int end;
        int ret;
        int v1,v2,v3;
        std::map<int,int> m;
        std::map<int,int>::iterator it;
        std::map<int,int>::iterator itend;
        int vnum;
        int tnum;

        mesh = pmesh->getChangedMesh();

        tnum=0;
        // collect indices of the active triangles
        end = mesh->getNumTriangles();
        for(int i=0; i<end; i++) {
            jeff::triangle& t=mesh->getTri(i);
            if(t.isActive()) {
                t.getVerts(v1, v2, v3);
                m[v1]=1;
                m[v2]=1;
                m[v3]=1;
                tnum++;
            }
        }

        vnum = m.size();
        assert(tnum==pmesh->numVisTris());

        // create (or append to) the output ply file
        oply = ply_create_append(filename.c_str(), storage_mode, NULL, append);
        if(!oply) {
            cout<<"error: unable to open file '"<<filename<<"' for writing"<<endl<<flush;
            return false;
        }

        // add lod's number
        if(!ply_add_obj_info(oply,lod_info.c_str())) {
            cout<<"error: unable to add object info to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply); // we don't care about the result              [*]
                            // since there has been an error already
            return false;
        }

        // add vertex element and its properties
        if(!ply_add_element(oply,"vertex",vnum)) {
            cout<<"error: unable to add element "<<"'vertex'"<<" to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }

                    // we store all of the meshes using Mesh and PMesh,
                    // and they keep the data in float format
        ret = ply_add_scalar_property(oply,"x",PLY_FLOAT);
        ret = ret && ply_add_scalar_property(oply,"y",PLY_FLOAT);
        ret = ret && ply_add_scalar_property(oply,"z",PLY_FLOAT);
        if(!ret) {
            cout<<"error: unable to add some of "<<"'vertex'"<<" element"
                <<" properties to ply object of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }


        // add face (triangle) element and its properties
        if(!ply_add_element(oply,"face",tnum)) {
            cout<<"error: unable to add element "<<"'face'"<<" to ply object of file '"
                <<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }

        if(!ply_add_list_property(oply,"vertex_indices",PLY_UINT8,PLY_INT)) {
            cout<<"error: unable to add 'vertex_indices' property "<<"of 'face' element"
                <<" to ply object of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);// [*]
            return false;
        }
        

        // write the header
        if(!ply_write_header(oply)) {
            cout<<"error: failed writing header of file '"<<filename<<"'"<<endl<<flush;
            ply_close(oply);    // [*]
            return false;
        }

        // write the vertices
        it=m.begin();
        itend=m.end();
        jeff::Vec3 v;
        for(int i=0; it!=itend; it++, i++) {
            v=(mesh->getVertex(it->first)).getXYZ();
            ret=ply_write(oply,v.x);
            ret=ret && ply_write(oply,v.y);
            ret=ret && ply_write(oply,v.z);
            if(!ret) {
                cout<<"error: failed writing vertex "<<i<<"th"<<endl<<flush;
                ply_close(oply);// [*]
                return false;
            }
            //it->second = i;
            m[it->first] = i;
        }

        // write the triangles
        end = mesh->getNumTriangles();
        int k=0;
        for(int i=0; i<end; i++) {
            jeff::triangle& t=mesh->getTri(i);
            if(t.isActive()) {
                t.getVerts(v1, v2, v3);
                ret=ply_write(oply,3);
                ret=ret && ply_write(oply,m[v1]);
                ret=ret && ply_write(oply,m[v2]);
                ret=ret && ply_write(oply,m[v3]);
                if(!ret) {
                    cout<<"error: failed writing triangle "<<k<<"th"<<endl<<flush;
                    ply_close(oply);// [*]
                    return false;
                }
                k++;
            }
        }


        if (!ply_close(oply)) {
            cout<<"error closing file '"<<filename<<"'"<<endl<<flush;
            return false;
        }
        else {
            cout<<"saved "<<lod_info<<" to file '"<<filename<<"'"<<endl<<flush;
            return true;
        }
    }


    void save_lods(std::string filename, e_ply_storage_mode storage_mode=PLY_ASCII)
    {
        assert(jmesh!=NULL); // otherwise it would be an empty file 
                            // (jmesh==NULL => all of the pmeshes==NULL)
        save_one_lod(jmesh,get_lod_info(0),filename,storage_mode,false);
        int k=1;
        int end=jpmeshes.size();
        
        for(int i=0; i<end; i++)
            if(jpmeshes[i]!=NULL)
                if(!save_one_lod(jpmeshes[i],get_lod_info(k++),
                                    filename,storage_mode,true)) {
                    cout<<"error: unable to write '"<<get_lod_info(k-1)<<"'"
                        <<", stopping further writing"<<endl<<flush;
                    break;
                }
    }


    std::string get_lod_info(int num)
    {
        return "LOD "+int2str(num);
    }





    class enum_method 
    {
        public:
        enum type { QUADRIC=0, QUADRICTRI, MELAX, SHORTEST, ENDENUM };
    };

    

    // todo all below okay
    private:
    static const char* text[];
    static const int yinc_text[];
    static const int xoffset_text[];
    static bool show_text[];
    static bool menu_pos[];
    static const int text_count;

    static const char* help_str[];
    static const int help_count;

    int width;
    int height;

    bool ob_loaded;

    bool typing;
    std::string typed;

    //! the original object
    jeff::Mesh* jmesh;
    //! the meshes of LODs
    std::vector<jeff::PMesh*> jpmeshes;
    std::vector<jeff::Mesh*> extra_base_meshes;

    /*! an index of the active LOD (counted from 0, includes the original LOD,
     *  NONE_ACTIVE==no active LOD */
    int active_lod;         
    
    lighting li;
    
    //! the numbers of the displayed discrete LODs, including the original
    std::vector<bool> show_lods;

    enum_method::type method;

    // percent of the triangles of the original mesh that will be removed
    float tr_perc;
    // a number of the triangles that will be removed in the next collapse
    int tr_num;

    bool display_ob;
    bool illuminated;
    bool polygon_mode; // false ~= wireframe, true ~= normal

    jeff_camera jcam;

    std::string ob_name;

    static const short START_ACT_MENU_POS;// = 0;
    //static const bool HELP_SHOWED_ON_START;// = false;
    static const int NONE_ACTIVE = -1;
};


#endif // LOD_GEN_DISP_H
